package icu.funkye.confine.spring.interceptor;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import icu.funkye.confine.ConfineFilter;
import icu.funkye.confine.exception.ConfineException;
import icu.funkye.confine.failHandle.ConfineFailHandle;
import icu.funkye.confine.limiter.RateLimiterFactory;
import icu.funkye.confine.wrapper.ResourceWrapper;
import com.google.common.base.Stopwatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import static icu.funkye.confine.constants.ConfineConstant.DEFAULT_METHOD;
import static icu.funkye.confine.exception.ConfineException.ConfineExceptionCode.RT;

/**
 * @author jianbin.chen
 */
public class ConfineInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(ConfineInterceptor.class);

    private static final ThreadLocal<Stopwatch> threadLocal = new ThreadLocal<>();

    private ConfineFailHandle failHandle;

    private int globalMaxTps;

    private Long globalMaxRt;

    private ResourceWrapper resourceWrapper;

    public ConfineInterceptor(ConfineFailHandle failHandle, int globalMaxTps, Long globalMaxRt) {
        this.failHandle = failHandle;
        this.globalMaxTps = globalMaxTps;
        this.globalMaxRt = globalMaxRt;
        this.resourceWrapper = RateLimiterFactory.create(DEFAULT_METHOD, globalMaxTps, globalMaxRt);
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws IOException {
        threadLocal.set(Stopwatch.createStarted());

        try {
            // business
            ConfineFilter.entry(DEFAULT_METHOD);
            return true;
        } catch (ConfineException e) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(e.getMessage());
            }
            failHandle.failBack(request, response);
            return false;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
        throws Exception {
        Stopwatch stopwatch = threadLocal.get();
        threadLocal.remove();
        Long rt = stopwatch.elapsed(TimeUnit.MILLISECONDS);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("the entrance current rt: {}", rt);
        }
        ConfineException confineException = null;
        if (ex instanceof ConfineException) {
            confineException = (ConfineException)ex;
        }
        if (rt > 0 && (confineException == null || confineException.getCode() != RT)) {
            this.resourceWrapper.addRt(rt);
        }
    }

    public ConfineFailHandle getFailHandle() {
        return failHandle;
    }

    public void setFailHandle(ConfineFailHandle failHandle) {
        this.failHandle = failHandle;
    }

    public int getGlobalMaxTps() {
        return globalMaxTps;
    }

    public Long getGlobalMaxRt() {
        return globalMaxRt;
    }

    public void setGlobalMaxRt(Long globalMaxRt) {
        this.resourceWrapper.setMaxRt(globalMaxRt);
        this.globalMaxRt = globalMaxRt;
    }

    public void setGlobalMaxTps(int globalMaxTps) {
        this.resourceWrapper.getRateLimiter().setTps(globalMaxTps);
        this.globalMaxTps = globalMaxTps;
    }
}
